/* eslint-disable no-use-before-define */
import { BigInteger } from './jsbn';
import { encodeDer, decodeDer } from './asn1';
import {
    getGlobalCurve,
    generateEcparam,
    generateKeyPairHex,
    compressPublicKeyHex,
    utf8ToHex,
    leftPad,
    arrayToHex,
    arrayToUtf8,
    hexToArray,
    verifyPublicKey,
    comparePublicKeyHex,
} from './utils';
import { sm3Before } from './sm3';

const {G, curve, n} = generateEcparam()
const C1C2C3 = 0

/**
 * 加密
 */
function doEncrypt(msg, publicKey, cipherMode = 1) {
    msg = typeof msg === 'string' ? hexToArray(utf8ToHex(msg)) : Array.prototype.slice.call(msg)
    publicKey = getGlobalCurve().decodePointHex(publicKey) // 先将公钥转成点

    const keypair = generateKeyPairHex()
    const k = new BigInteger(keypair.privateKey, 16) // 随机数 k

    // c1 = k * G
    let c1 = keypair.publicKey
    if (c1.length > 128) c1 = c1.substr(c1.length - 128)

    // (x2, y2) = k * publicKey
    const p = publicKey.multiply(k)
    const x2 = hexToArray(leftPad(p.getX().toBigInteger().toRadix(16), 64))
    const y2 = hexToArray(leftPad(p.getY().toBigInteger().toRadix(16), 64))

    // c3 = hash(x2 || msg || y2)
    const c3 = arrayToHex(sm3Before([].concat(x2, msg, y2)))

    let ct = 1
    let offset = 0
    let t = [] // 256 位
    const z = [].concat(x2, y2)
    const nextT = () => {
        // (1) Hai = hash(z || ct)
        // (2) ct++
        t = sm3Before([...z, ct >> 24 & 0x00ff, ct >> 16 & 0x00ff, ct >> 8 & 0x00ff, ct & 0x00ff])
        ct++
        offset = 0
    }
    nextT() // 先生成 Ha1

    for (let i = 0, len = msg.length; i < len; i++) {
        // t = Ha1 || Ha2 || Ha3 || Ha4
        if (offset === t.length) nextT()

        // c2 = msg ^ t
        msg[i] ^= t[offset++] & 0xff
    }
    const c2 = arrayToHex(msg)

    return cipherMode === C1C2C3 ? c1 + c2 + c3 : c1 + c3 + c2
}

/**
 * 解密
 */
function doDecrypt(encryptData, privateKey, cipherMode = 1, {
    output = 'string',
} = {}) {
    privateKey = new BigInteger(privateKey, 16)

    let c3 = encryptData.substr(128, 64)
    let c2 = encryptData.substr(128 + 64)

    if (cipherMode === C1C2C3) {
        c3 = encryptData.substr(encryptData.length - 64)
        c2 = encryptData.substr(128, encryptData.length - 128 - 64)
    }

    const msg = hexToArray(c2)
    const c1 = getGlobalCurve().decodePointHex('04' + encryptData.substr(0, 128))

    const p = c1.multiply(privateKey)
    const x2 = hexToArray(leftPad(p.getX().toBigInteger().toRadix(16), 64))
    const y2 = hexToArray(leftPad(p.getY().toBigInteger().toRadix(16), 64))

    let ct = 1
    let offset = 0
    let t = [] // 256 位
    const z = [].concat(x2, y2)
    const nextT = () => {
        // (1) Hai = hash(z || ct)
        // (2) ct++
        t = sm3Before([...z, ct >> 24 & 0x00ff, ct >> 16 & 0x00ff, ct >> 8 & 0x00ff, ct & 0x00ff])
        ct++
        offset = 0
    }
    nextT() // 先生成 Ha1

    for (let i = 0, len = msg.length; i < len; i++) {
        // t = Ha1 || Ha2 || Ha3 || Ha4
        if (offset === t.length) nextT()

        // c2 = msg ^ t
        msg[i] ^= t[offset++] & 0xff
    }

    // c3 = hash(x2 || msg || y2)
    const checkC3 = arrayToHex(sm3Before([].concat(x2, msg, y2)))

    if (checkC3 === c3.toLowerCase()) {
        return output === 'array' ? msg : arrayToUtf8(msg)
    } else {
        return output === 'array' ? [] : ''
    }
}

/**
 * 签名
 */
function doSignature(msg, privateKey, {
    pointPool, der, hash, publicKey, userId
} = {}) {
    let hashHex = typeof msg === 'string' ? utf8ToHex(msg) : arrayToHex(msg)

    if (hash) {
        // sm3杂凑
        publicKey = publicKey || getPublicKeyFromPrivateKey(privateKey)
        hashHex = getHash(hashHex, publicKey, userId)
    }

    const dA = new BigInteger(privateKey, 16)
    const e = new BigInteger(hashHex, 16)

    // k
    let k = null
    let r = null
    let s = null

    do {
        do {
            let point
            if (pointPool && pointPool.length) {
                point = pointPool.pop()
            } else {
                point = getPoint()
            }
            k = point.k

            // r = (e + x1) mod n
            r = e.add(point.x1).mod(n)
        } while (r.equals(BigInteger.ZERO) || r.add(k).equals(n))

        // s = ((1 + dA)^-1 * (k - r * dA)) mod n
        s = dA.add(BigInteger.ONE).modInverse(n).multiply(k.subtract(r.multiply(dA))).mod(n)
    } while (s.equals(BigInteger.ZERO))

    if (der) return encodeDer(r, s) // asn.1 der 编码

    return leftPad(r.toString(16), 64) + leftPad(s.toString(16), 64)
}

/**
 * 验签
 */
function doVerifySignature(msg, signHex, publicKey, {der, hash, userId} = {}) {
    let hashHex = typeof msg === 'string' ? utf8ToHex(msg) : arrayToHex(msg)

    if (hash) {
        // sm3杂凑
        hashHex = getHash(hashHex, publicKey, userId)
    }

    let r;
    let
        s
    if (der) {
        const decodeDerObj = decodeDer(signHex) // asn.1 der 解码
        r = decodeDerObj.r
        s = decodeDerObj.s
    } else {
        r = new BigInteger(signHex.substring(0, 64), 16)
        s = new BigInteger(signHex.substring(64), 16)
    }

    const PA = curve.decodePointHex(publicKey)
    const e = new BigInteger(hashHex, 16)

    // t = (r + s) mod n
    const t = r.add(s).mod(n)

    if (t.equals(BigInteger.ZERO)) return false

    // x1y1 = s * G + t * PA
    const x1y1 = G.multiply(s).add(PA.multiply(t))

    // R = (e + x1) mod n
    const R = e.add(x1y1.getX().toBigInteger()).mod(n)

    return r.equals(R)
}

/**
 * sm3杂凑算法
 */
function getHash(hashHex, publicKey, userId = '1234567812345678') {
    // z = hash(entl || userId || a || b || gx || gy || px || py)
    userId = utf8ToHex(userId)
    const a = leftPad(G.curve.a.toBigInteger().toRadix(16), 64)
    const b = leftPad(G.curve.b.toBigInteger().toRadix(16), 64)
    const gx = leftPad(G.getX().toBigInteger().toRadix(16), 64)
    const gy = leftPad(G.getY().toBigInteger().toRadix(16), 64)
    let px
    let py
    if (publicKey.length === 128) {
        px = publicKey.substr(0, 64)
        py = publicKey.substr(64, 64)
    } else {
        const point = G.curve.decodePointHex(publicKey)
        px = leftPad(point.getX().toBigInteger().toRadix(16), 64)
        py = leftPad(point.getY().toBigInteger().toRadix(16), 64)
    }
    const data = hexToArray(userId + a + b + gx + gy + px + py)

    const entl = userId.length * 4
    data.unshift(entl & 0x00ff)
    data.unshift(entl >> 8 & 0x00ff)

    const z = sm3Before(data)

    // e = hash(z || msg)
    return arrayToHex(sm3Before(z.concat(hexToArray(hashHex))))
}

/**
 * 计算公钥
 */
function getPublicKeyFromPrivateKey(privateKey) {
    const PA = G.multiply(new BigInteger(privateKey, 16))
    const x = leftPad(PA.getX().toBigInteger().toString(16), 64)
    const y = leftPad(PA.getY().toBigInteger().toString(16), 64)
    return '04' + x + y
}

/**
 * 获取椭圆曲线点
 */
function getPoint() {
    const keypair = generateKeyPairHex()
    const PA = curve.decodePointHex(keypair.publicKey)

    keypair.k = new BigInteger(keypair.privateKey, 16)
    keypair.x1 = PA.getX().toBigInteger()

    return keypair
}

export {
    generateKeyPairHex,
    compressPublicKeyHex,
    comparePublicKeyHex,
    doEncrypt,
    doDecrypt,
    doSignature,
    doVerifySignature,
    getPublicKeyFromPrivateKey,
    getPoint,
    verifyPublicKey,
}
